package com.muchenx.service;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.muchenx.db.DatasourceHelper;
import com.muchenx.util.ReflectUtil;
import org.apache.commons.lang3.StringUtils;

import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 代码生成器
 */
public class AutoCodeGenerator {

    private final static Map<String, List<Field>> CLS_FIELD_CACHE = new HashMap<>();

    public static AutoCodeGenerator create() {
        return new AutoCodeGenerator();
    }

    /**
     * 代码生成核心类
     *
     * @param tables       - 表名（多多张表以英文逗号分隔）
     * @param superCls     - 实体类父类（可选）
     * @param superPackage - 生成代码父包名
     * @param subPackage   - 生成代码子包名
     * @return - 代码生成路径
     */
    public String generate(String tables, Class<?> superCls, String superPackage, String subPackage,
                           Map<OutputFile, String> enableTemplates) throws Throwable {
        File projectPath = initHomeDir(tables);
        File javaPath = new File(projectPath.getAbsolutePath() + File.separator + "java");
        File xmlPath = new File(projectPath.getAbsolutePath() + File.separator + "xml");
        if (!javaPath.exists() && javaPath.mkdirs()) {
        }
        if (!xmlPath.exists() && xmlPath.mkdirs()) {
        }
        // 数据库配置
        FastAutoGenerator.create(new DataSourceConfig.Builder(DatasourceHelper.getDatasource()))
                // 全局配置
                .globalConfig((scanner, builder) -> {
                    builder.author(System.getProperty("user.name"))
                            .outputDir(javaPath.getAbsolutePath())// 指定输出目录
                            .disableOpenDir()// 禁止打开输出目录
                            .dateType(DateType.TIME_PACK)// 时间策略
                            .commentDate("yyyy-MM-dd HH:mm:ss").build();// 类注释日期的格式
                })
                // 包配置
                .packageConfig((scanner, builder) -> {
                    builder.parent(superPackage)// 父包名
                            .moduleName(subPackage)// 模块名（子包名）
                            .entity("entity")// Entity 包名
                            .service("service")// Service 包名
                            .serviceImpl("service.impl")//	Service Impl 包名
                            .controller("controller")// Controller 包名
                            .mapper("mapper")// Mapper 包名
                            .xml("mapper")// MapperXML 包名
                            .pathInfo(new HashMap<>() {{
                                put(OutputFile.xml, xmlPath.getAbsolutePath());
                            }});// 路径配置信息
                })
                .strategyConfig((scanner, builder) -> {//策略配置
                    // 增加表匹配(内存过滤)，	include 与 exclude 只能配置一项
                    builder.addInclude(getTables(tables))
                            // 	增加表排除匹配(内存过滤)，	include 与 exclude 只能配置一项
                            // .addExclude("请输入要忽略的表名，多个英文逗号分隔？")
                            // 	增加过滤表后缀
                            .addTableSuffix("")
                            .addTablePrefix("");// 	增加过滤表前缀

                    // service 策略配置
                    builder.serviceBuilder()
                            .serviceTemplate(enableTemplates.get(OutputFile.service))
                            .serviceImplTemplate(enableTemplates.get(OutputFile.serviceImpl))
                            // 	格式化文件名称
                            .formatServiceFileName("%sService")
                            .formatServiceImplFileName("%sServiceImpl");

                    // 实体策略配置
                    builder.entityBuilder()
                            .javaTemplate(enableTemplates.get(OutputFile.entity))
                            .idType(IdType.ASSIGN_ID)// 指定生成的主键的ID类型（雪花算法）
                            .naming(NamingStrategy.underline_to_camel)// 数据库表映射到实体的命名策略（下划线转驼峰命名）
                            .columnNaming(NamingStrategy.underline_to_camel)// 数据库表字段映射到实体的命名策略（下划线转驼峰命名）
                            .enableLombok()// 开启 lombok 模型
                            .enableTableFieldAnnotation()// 开启生成实体时生成字段注解
                            .enableChainModel();// 开启链式模型
                    if (superCls != null) {
                        builder.entityBuilder()
                                .versionColumnName(getVersionColumn(superCls, true))// 设置乐观锁数据库表字段名称
                                .versionPropertyName(getVersionColumn(superCls, false))// 设置乐观锁实体属性字段名称
                                .logicDeleteColumnName(getLogicDeletedColumn(superCls, true))// 逻辑删除数据库字段名称
                                .logicDeletePropertyName(getLogicDeletedColumn(superCls, false))// 逻辑删除实体属性名称
                                .superClass(superCls)// 父类名称（含包名）
                                .addSuperEntityColumns(getBaseColumns(superCls));// 添加父类公共字段
                    }
                    // controller 策略配置
                    builder.controllerBuilder()
                            .template(enableTemplates.get(OutputFile.controller))
                            .formatFileName("%sController")
                            .enableRestStyle()// 开启生成@RestController 控制器
                            .enableHyphenStyle();// 开启驼峰转连字符
                    // 	mapper 策略配置
                    builder.mapperBuilder()
                            .mapperTemplate(enableTemplates.get(OutputFile.mapper))
                            .mapperXmlTemplate(enableTemplates.get(OutputFile.xml))
                            .superClass(com.baomidou.mybatisplus.core.mapper.BaseMapper.class)// 设置父类
                            .formatMapperFileName("%sMapper")
                            .mapperAnnotation(org.apache.ibatis.annotations.Mapper.class)// 标记 Mapper 注解
                            .formatXmlFileName("%sMapper")
                            .enableBaseResultMap()// 生成公共字段映射及字段
                            .enableBaseColumnList();// 生成公共数据库字段
                })
                // 使用Freemarker引擎模板，默认的是Velocity引擎模板
                .templateEngine(new FreemarkerTemplateEngine()).execute();
        return projectPath.getAbsolutePath();
    }

    private File initHomeDir(String tables) {
        File honeDir = FileSystemView.getFileSystemView().getHomeDirectory();// 桌面路径
        String rootDir = honeDir.getAbsolutePath() + File.separator + "CODE_"
                + tables.replaceAll(",", "&") + "_"
                + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmddss"));
        File projectPath = new File(rootDir);
        if (projectPath.exists()) {
            projectPath.delete();
        }
        projectPath.mkdirs();
        return projectPath;
    }

    /**
     * 处理 all 情况
     */
    private List<String> getTables(String tables) {
        return "all".equalsIgnoreCase(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

    private String getVersionColumn(Class<?> superCls, boolean ifReturnColumnName) {
        return getSpecialFieldName(superCls, Version.class, ifReturnColumnName);
    }

    private String getLogicDeletedColumn(Class<?> superCls, boolean ifReturnColumnName) {
        return getSpecialFieldName(superCls, TableLogic.class, ifReturnColumnName);
    }

    private static String getSpecialFieldName(
            Class<?> superCls, Class<? extends Annotation> anno, boolean ifReturnColumnName) {
        Optional<Field> first = getAllFields(superCls).stream()
                .filter(f -> f.isAnnotationPresent(TableField.class) && f.isAnnotationPresent(anno))
                .findFirst();
        if (first.isPresent()) {
            if (ifReturnColumnName) {
                return first.get().getAnnotation(TableField.class).value();
            }
            return first.get().getName();
        }
        return null;
    }

    private String[] getBaseColumns(Class<?> superCls) {
        Map<String, Void> map = new HashMap<>();
        getAllFields(superCls).forEach(e -> {
            TableField tableField = e.getAnnotation(TableField.class);
            if (tableField != null && !StringUtils.isEmpty(tableField.value())) {
                map.put(tableField.value(), null);
            }
        });
        return map.keySet().toArray(new String[0]);
    }

    private static List<Field> getAllFields(Class<?> superCls) {
        return CLS_FIELD_CACHE.computeIfAbsent("super-cls-fields-" + superCls.getName(), k -> ReflectUtil.getAllFields(superCls));
    }

}

